package com.itjeffrey.autocode.aspect;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import com.itjeffrey.autocode.anno.JobLogger;
import com.itjeffrey.autocode.bo.ScheduleJobBO;
import com.itjeffrey.autocode.bo.ScheduleJobLogTbBO;
import com.itjeffrey.autocode.common.Result;
import com.itjeffrey.autocode.constant.SysConstant;
import com.itjeffrey.autocode.service.ScheduleJobLogTbService;
import com.itjeffrey.autocode.service.ScheduleJobTbService;
import com.itjeffrey.autocode.util.ACBaseUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 日志切面
 *
 * @From: Jeffrey
 * @Date: 2021/3/24
 */
@Aspect //切面
@Component
public class LoggerAspect {

    public static final Logger log = LoggerFactory.getLogger(LoggerAspect.class);

    @Resource
    private ScheduleJobTbService scheduleJobTbService;

    @Resource
    private ScheduleJobLogTbService scheduleJobLogTbService;

    /**
     * 控制器接口日志切入点
     */
    @Pointcut("execution(* com.itjeffrey.autocode.controller.*.*(..))")
    public void loggerPointcut() {}

    /**
     * 定时任务日志切入点
     */
    @Pointcut("@annotation(com.itjeffrey.autocode.anno.JobLogger)")
    public void jobLoggerPointcut() {}

    /**
     * 前置通知
     * 给每个进入Controller中方法的请求进行参数日志输出
     */
    @Before("loggerPointcut()")
    public void doBefore(JoinPoint joinPoint) {
        //方法参数信息
        Object[] args = joinPoint.getArgs();
        List<Object> logArgs = ACBaseUtil.arrToStream(args)
                .filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse) && !(arg instanceof MultipartFile)))
                .collect(Collectors.toList());
        //注意此处可能会导致JSON序列化异常，若序列化失败，则将请求参数类型放到以上filter中排除即可
        String argStr = JSON.toJSONString(logArgs);
        log.info("方法参数信息 -> {}", argStr);
        //方法签名
        Signature signature = joinPoint.getSignature();
        String signatureName = signature.getName();
        String className = signature.getDeclaringType().getSimpleName();

        //请求参数
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes
                .REFERENCE_REQUEST);

        Map<String, String> map = Maps.newHashMap();
        Enumeration<String> parameterNames = request.getParameterNames();
        while (parameterNames.hasMoreElements()) {
            String s = parameterNames.nextElement();
            map.put(s, request.getParameter(s));
        }
        String paraStr = JSON.toJSONString(map);
        log.info("接口 {} {} 请求参数：{}", className, signatureName, paraStr);

    }

    /**
     * 后置通知
     */
    @After("loggerPointcut()")
    public void doAfter(JoinPoint joinPoint) {
//        log.info("接口执行完毕！");
    }

    /**
     * 后置异常通知
     */
    @AfterThrowing(value = "loggerPointcut()", throwing = "exception")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable exception) {
        String name = joinPoint.getSignature().getName();
        String className = joinPoint.getSignature().getDeclaringType().getSimpleName();
        log.warn("接口 {} {} 发生异常！", className, name);
    }

    /**
     * 后置返回通知
     */
    @AfterReturning(value = "loggerPointcut()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) {
        if (Objects.nonNull(result) && !(result instanceof ResponseEntity)) {
            log.warn("接口响应结果类型不正确！");
        }
        String name = joinPoint.getSignature().getName();
        String className = joinPoint.getSignature().getDeclaringType().getSimpleName();
        log.info("接口 {} {} 响应参数：{}", className, name, JSON.toJSONString(result));
    }

    @Around(value = "jobLoggerPointcut()")
    public void doJobLoggerAround(ProceedingJoinPoint joinPoint){
        //方法参数信息
        Object[] args = joinPoint.getArgs();
        List<Object> logArgs = ACBaseUtil.arrToStream(args)
                .filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse) && !(arg instanceof MultipartFile)))
                .collect(Collectors.toList());
        //注意此处可能会导致JSON序列化异常，若序列化失败，则将请求参数类型放到以上filter中排除即可
        String argStr = JSON.toJSONString(logArgs);
        log.info("Scheduler Job execute START=====job params: {}", argStr);
        //方法签名
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature)signature;
        Method method = methodSignature.getMethod();
        Method declaredMethod;
        try {
            declaredMethod = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(),
                    method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            return;
        }
        JobLogger jobLogger = AnnotationUtils.getAnnotation(declaredMethod, JobLogger.class);
        //构造任务日志BO，以数据库数据为主
        ScheduleJobLogTbBO scheduleJobLogTbBO = new ScheduleJobLogTbBO();
        String simpleName = joinPoint.getTarget().getClass().getSimpleName();
        Result result = scheduleJobTbService.queryByBeanName(simpleName);
        if(Objects.isNull(result.getData())){
            log.error("Scheduler Job execute END!===Job not found, beanName:{}", simpleName);
            return;
        }
        ScheduleJobBO scheduleJobBO = JSONObject.toJavaObject((JSONObject)JSONObject.toJSON(result.getData()), ScheduleJobBO.class);
        scheduleJobLogTbBO.setJobId(scheduleJobBO.getJobId());
        scheduleJobLogTbBO.setExecuteTime(new Date());
        scheduleJobLogTbBO.setJobName(StringUtils.isNotBlank(scheduleJobBO.getJobName()) ?
                scheduleJobBO.getJobName() : (StringUtils.isBlank(jobLogger.value()) ? jobLogger.name() : jobLogger.value()));
        scheduleJobLogTbBO.setBeanName(scheduleJobBO.getBeanName());
        scheduleJobLogTbBO.setGroupId(scheduleJobBO.getGroupId());
        scheduleJobLogTbBO.setJobType(scheduleJobBO.getJobType());
        //`logArgs` execute方法入参，即任务参数scheduleJobBO.getJobParams
        String methodParams = StringUtils.join(logArgs, SysConstant.SPECIAL_CHAR_3);
        String annoParams = jobLogger.jobParams();
        String jobParams = "";
        if(StringUtils.isBlank(methodParams)){
            if(StringUtils.isNotBlank(annoParams)){
                jobParams = annoParams;
            }
        }else {
            if(StringUtils.isBlank(annoParams)){
                jobParams = methodParams;
            }else {
                jobParams = methodParams + SysConstant.SPECIAL_CHAR_3 + annoParams;
            }
        }
        scheduleJobLogTbBO.setJobParams(jobParams);
        StopWatch stopWatch = new StopWatch();
        try {
            stopWatch.start();
            //执行任务
            joinPoint.proceed();
            stopWatch.stop();
            scheduleJobLogTbBO.setJobTime(new BigDecimal(stopWatch.getTotalTimeMillis()));
            scheduleJobLogTbBO.setResultCode(SysConstant.SCHEDULE_JOB_LOG_RESULT_CODE_OK);
            scheduleJobLogTbBO.setResultMsg("");
        }catch (Throwable e){
            e.printStackTrace();
            scheduleJobLogTbBO.setResultCode(SysConstant.SCHEDULE_JOB_LOG_RESULT_CODE_FAIL);
            scheduleJobLogTbBO.setResultMsg(e.getMessage());
        }finally {
            //任务执行完成，任务日志入库
            scheduleJobLogTbService.add(scheduleJobLogTbBO);
            log.info("Scheduler Job execute END");
        }
    }

}
